package com.zxj.common.security.service;

import cn.hutool.core.lang.UUID;
import com.zxj.common.core.contains.CacheConstants;
import com.zxj.common.core.contains.JwtConstants;
import com.zxj.common.core.domain.cache.LoginUserCache;
import com.zxj.common.core.utils.JwtUtils;
import com.zxj.common.redis.service.RedisService;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Token 服务
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class TokenService {

    // 引入redis服务
    private final RedisService redisService;


    /**
     * 创建token
     * @param userId 用户ID,数据库中的主键
     * @param secret jwt token 密钥
     * @param identity 用户身份 1：普通用户 2：管理员用户
     * @param nickName 用户昵称
     * @param headImage 头像URL
     * @return 返回生成的 jwt token
     */
    public String createToken(Long userId, String secret, Integer identity, String nickName, String headImage) {
        // 1. 生成 jwt token
        // 构建jwt的payload
        Map<String, Object> claims = new HashMap<>();
        String userKey = UUID.fastUUID().toString();
        claims.put(JwtConstants.LOGIN_USER_ID, userId);
        claims.put(JwtConstants.LOGIN_USER_KEY, userKey);
        // 生成token
        String token = JwtUtils.createToken(claims, secret);
        // 2. 第三方机制中存储敏感的信息, 将用户敏感信息缓存到redis中
        this.saveLoginUserCache(identity, nickName, headImage, userKey);
        // 3. 返回token
        return token;
    }

    /**
     * 保存登录用户信息到缓存中
     * @param identity 用户身份 1：普通用户 2：管理员用户
     * @param nickName 用户昵称
     * @param headImage 头像URL
     * @param userKey 生成的uuid 作为用户的唯一标识
     */
    private void saveLoginUserCache(Integer identity, String nickName, String headImage, String userKey) {
        // 2.1 生成 key 值
        String cacheTokenKey = this.getCacheTokenKeyByUserKey(userKey);
        // 2.2 生成 value 值
        LoginUserCache loginUserCache = LoginUserCache.builder()
                .nickName(nickName) // 用户昵称
                .identity(identity) // 用户身份 1：普通用户 2：管理员用户
                .headImage(headImage) // 头像URL
                .userKey(userKey) // 生成的uuid 作为用户的唯一标识
                .build();
        // 2.3 存储到redis中, 需要设置过期时间-用来判断JWT是否过期, 单位为分钟
        redisService.setCacheObject(cacheTokenKey, loginUserCache, CacheConstants.EXP, TimeUnit.MINUTES);
        log.debug("用户信息缓存成功, cacheTokenKey:{}, loginUserCache:{}", cacheTokenKey, loginUserCache);
    }


    /**
     * 依据用户key获取登录用户缓存
     * @param userKey 用户key
     * @return 登录用户缓存对象
     */
    public LoginUserCache getLoginUserCache(String userKey) {
        String cacheTokenKey = this.getCacheTokenKeyByUserKey(userKey);
        return redisService.getCacheObject(cacheTokenKey, LoginUserCache.class);
    }

    /**
     * 依据缓存对象刷新对应的缓存事件
     * @param loginUserCache 登录用户缓存
     */
    public void refreshToken(LoginUserCache loginUserCache) {
        // 1. 获得缓存的key
        String cacheTokenKey = getCacheTokenKeyByUserKey(loginUserCache.getUserKey());
        // 2. 刷新缓存 - 重新设置过期时间
        redisService.setCacheObject(cacheTokenKey, loginUserCache, CacheConstants.EXP, TimeUnit.MINUTES);
    }


    /**
     * 依据用户key生成redis中存储的key
     * @param userKey 可以唯一标识用户的key
     * @return redis中存储的key
     */
    public String getCacheTokenKeyByUserKey(String userKey) {
        return CacheConstants.LOGIN_TOKEN_KEY + userKey;
    }

    /**
     * 从token中获取Claims
     * @param token token
     * @param secret jwt token 密钥
     * @return Claims
     */
    public Claims getClaimsFromToken(String token, String secret) {
        Claims claims;
        try {
            claims = JwtUtils.parseToken(token, secret); //获取令牌中信息  解析payload中信息  存储着用户唯一标识信息
            if (claims == null) {
                log.error("解析token：{}, 出现异常", token);
                return null;
            }
        } catch (Exception e) {
            log.error("解析token：{}, 出现异常", token, e);
            return null;
        }
        return claims;
    }

    //在身份认证通过之后才会调用的，并且在请求到达controller层之前  在拦截器中调用
    public void extendToken(Claims claims) {
        // 1. 获取缓存中的key
        String userKey = getUserKey(claims);
        if (userKey == null) {
            return;
        }
        String cacheTokenKey = getCacheTokenKeyByUserKey(userKey);

        // 2. 判断token是否需要延长
        //720min  12个小时      剩余  180min 时候对它进行延长
        Long expire = redisService.getExpire(cacheTokenKey, TimeUnit.MINUTES);
        if (expire != null && expire < CacheConstants.REFRESH_TIME) {
            redisService.expire(cacheTokenKey, CacheConstants.EXP, TimeUnit.MINUTES);
        }
    }

    public Long getUserId(Claims claims) {
        if (claims == null) return null;
        return Long.valueOf(JwtUtils.getUserId(claims));  //获取jwt中的key
    }

    public String getUserKey(Claims claims) {
        if (claims == null) return null;
        return JwtUtils.getUserKey(claims);  //获取jwt中的key
    }

    private String getUserKey(String token, String secret) {
        Claims claims = getClaimsFromToken(token, secret);
        if (claims == null) return null;
        return JwtUtils.getUserKey(claims);  //获取jwt中的key
    }

    /**
     * 依据token获取登录用户缓存
     * @param token jwt token
     * @param secret jwt token 密钥
     * @return 登录用户缓存对象
     */
    public LoginUserCache getLoginUserCacheFromToken(String token, String secret) {
        Claims claims = getClaimsFromToken(token, secret);
        if (claims == null) {
            return null;
        }
        String userKey = getUserKey(claims);
        return getLoginUserCache(userKey);
    }

    /**
     * 删除登录用户信息
     * @param token jwt token
     * @param secret jwt token 密钥
     * @return true: 删除成功, false: 删除失败
     */
    public boolean deleteLoginUser(String token, String secret) {
        String userKey = getUserKey(token, secret);
        if (userKey == null) {
            return false;
        }
        return redisService.deleteObject(getCacheTokenKeyByUserKey(userKey));
    }

    /**
     * 刷新登录用户信息
     * @param nickName 用户昵称
     * @param headImage 头像URL
     * @param userKey 用户ID
     */
    public void refreshLoginUser(String nickName, String headImage, String userKey) {
        String tokenKey = getCacheTokenKeyByUserKey(userKey);
        LoginUserCache loginUser = redisService.getCacheObject(tokenKey, LoginUserCache.class);
        loginUser.setNickName(nickName);
        loginUser.setHeadImage(headImage);
        redisService.setCacheObject(tokenKey, loginUser);
    }
}
